Moving To Windows Part 3
Hidey ho stranger, last time we wrote most of the VV frame work and shoved our GDI code from tute1 into it. Now we must clean up some of the mess and formalize everything, starting with that nasty BGRA 32Bpp pixel format.
BGRA Bullocks
As you might have noticed, if you set a Color32_t to equal (0, 0xff, 0, 0) i.e. straight red then it would come out as green! This occurs because GDI 32bpp format is BGRA and our Color32_t format is ARGB, to remedy this we must do some color space conversions.
To convert BGRA to ARGB is very simple, all we do is swap the byte order. This can be accomplished by using shifts and masks.
// convert from 32bpp ARGB to BGRA
ARGB = ((b<<24)&0xff000000) + ((g<<16)&0x00ff0000) +((r<<8)&0x0000ff00);
We could be more cunning and just copy the bytes over but I'll leave that one up to you. Next we must do this for all the pixels in our VPage and place them in a separate memory page. Using data pointers we'll loop over every pixel, convert, then write it. This will all be done in the Flip() routine as Flip is called when the frame is complete and thus also ready for conversion.
void GDI_Flip( void )
{
RECT client;
HDC hDC;
int i;
unsigned short *WData;
unsigned int *DData;
Color32_t *VPData;
unsigned int *DIBData;
// set up pointers to destination and source
DData = m_DPage;
VPData = m_VPage;
// for all pixels
for (i=0; i < 320*240; i++)
{
// convert from 32bpp ARGB to BGRA
*DData = ((VPData->b<<24)&0xff000000) +
((VPData->g<<16)&0x00ff0000) +
((VPData->r<<8)&0x0000ff00);
// next pixel
DData++;
VPData++;
}
Where m_DPage is our second BGRA memory page, m_VPage (from tute2) is our ARGB Virtual Page.
Now instead of using m_VPage as the data source for our DIB we use m_DData. As we'll be extending the GDI interface for 16bpp, I'll assign DIBData to m_DData and use DIBData for the StreachDIBs() call.
// set DIB's Source data
DIBData = m_DPage;
//
// draw it to our window
//
// get the windows Device Context
hDC = GetDC( g_hWnd );
// if it's available
if (hDC != NULL)
{
// Get the window dimensions
GetClientRect(g_hWnd, &client);
// Stretch it
StretchDIBits( hDC,
0, // Destination top left hand corner
// X Position
0, // Destination top left hand corner
// Y Position
client.right, // Destinations Width
client.bottom, // Destinations height
0, // Source top left hand corner's
// X Position
0, // Source top left hand corner's
// Y Position
320, // Sources width
240, // Sources height
DIBData, // Source's data
&m_bi, // Bitmap Info
DIB_RGB_COLORS, // operations
SRCCOPY);
// now release the Device Context.
ReleaseDC( g_hWnd, hDC );
}
And that's it, we have our lovely ARGB 32Bpp format and when you write Color32_t.r = 0xff you actually get a Red pixel!
Methamphetaspeed
Speed is good, I like speed and although our Framework isn't the most speedy thing letting GDI do color space conversions just don't cut it. I mentioned when using GDI you want to use a DIB the same pixel depth as the current desktop, if you don't believe me try it and watch it crawl. So what do we do? Well detect the desktop bpp and create and use a DIB of that depth. We'll ignore 8bit and assume 16bpp or 24/32bpp.
To detect the desktop's bpp use GetDeviceCaps() with the BITSPIXEL parameter, it needs a DeviceContex to work which we covered back in tute1 so I'll just paste the code.
HDC hDC;
// get the windows Device Context
hDC = GetDC( g_hWnd );
if (hDC == NULL)
{
return 1;
}
// work out what the desktop bpp is
m_Bpp = GetDeviceCaps( hDC, BITSPIXEL);
// close resource
ReleaseDC( g_hWnd, hDC );
So once we have set our module variable m_Bpp we setup the Bitmap Header Info as follows:
// Set most of the header info fields
memset( &m_bi.bmiHeader, 0, sizeof(m_bi.bmiHeader) );
m_bi.bmiHeader.biSize = sizeof(m_bi.bmiHeader);
m_bi.bmiHeader.biWidth = 320;
m_bi.bmiHeader.biHeight = -240; // NOTE: -240. this is
m_bi.bmiHeader.biPlanes = 1; // because DIBs are upside down
m_bi.bmiHeader.biBitCount = m_Bpp;
m_bi.bmiHeader.biCompression = BI_BITFIELDS;
Now because we are supporting multiple bit depths our Color masks and VPage memory will be different. Using two memory pointers we'll specify a 16bpp W(ord)Page and a 32bpp D(word)Page respectively.
static unsigned short *m_WPage = NULL;
static unsigned int *m_DPage = NULL;
We'll allocate memory for these when specify our DIB Pixelformat (in GDI_Init()) using m_Bpp. It goes like this.
// create a dib the same bit depth as the desktop, as GDI color space
// conversion are very slow
switch (m_Bpp)
{
// 16bpp
case 16:
// set format 555
((unsigned long*)m_bi.bmiColors)[0]=0x00007c00;
((unsigned long*)m_bi.bmiColors)[1]=0x000003e0;
((unsigned long*)m_bi.bmiColors)[2]=0x0000001f;
// allocate 16 GDI memory
m_WPage = (unsigned short *)malloc( 320 * 240 *
sizeof(unsigned short) );
break;
// 24/32bpp
case 24:
case 32:
// 32bit color 888
((unsigned long*)m_bi.bmiColors)[0]=0x00ff0000;
((unsigned long*)m_bi.bmiColors)[1]=0x0000ff00;
((unsigned long*)m_bi.bmiColors)[2]=0x000000ff;
// allocate 32bit GDI memory
m_DPage = (unsigned int *)malloc( 320 * 240 *
sizeof(unsignedint) );
break;
// unknown
default:
return 1;
}
It's fairly straight forward, we treat 24bpp as 32bpp as they are very similar, and we'll return an error code if we don't understand the pixel format.
16 Bottles of Beer on the screen
Once we've got this 16bpp 555 DIB, we need to convert our 32bpp 8888 format to this. It's quite straight forward, simple bit shifting and masking does the job.
*Data = ((b>>3)&0x001f) + ((g<<2)&0x03e0) + ((r<<7)&0x7c00);
I've put heaps of brackets around everything because C/C++ has some less intuitive operator precedence rules and it's always best to be sure. We have to convert all pixels and will use basically the same loop as the above BGRA->ARGB conversion. This will also be done in the Flip() routine and is pretty simple so hears the New GDI_Flip() routine. We get the desktop's bpp and convert our VPage based on that then StreachDIBs() it as per usual.
/*************************************************************************
*
* Function: GDI_Flip()
*
* Desc: Copies the virtual page to the window
*
* Notes:
*
*************************************************************************/
void GDI_Flip( void )
{
RECT client;
HDC hDC;
int i;
unsigned short *WData;
unsigned int *DData;
Color32_t *VPData;
unsigned int *DIBData;
// convert the page to whatever format the desktop is at
switch (m_Bpp)
{
// 16bpp
case 16:
// set pointers to top of destination and source
// memory
WData = m_WPage;
VPData = m_VPage;
// for all pixels convert
for (i=0; i < 320*240; i++)
{
// convert VPage 32bpp 8888 data into
// 16bit 555
*WData = ((VPData->b>>3)&0x001f) +
((VPData->g<<2)&0x03e0) +
((VPData->r<<7)&0x7c00);
// next pixel
WData++;
VPData++;
}
// set the DIB's source data
DIBData = (unsigned int *)m_WPage;
break;
// 24/32 bpp
case 24:
case 32:
// set up pointers to destination and source
DData = m_DPage;
VPData = m_VPage;
// for all pixels
for (i=0; i < 320*240; i++)
{
// convert from 32bpp ARGB to BGRA
*DData = ((VPData->b<<24)&0xff000000) +
((VPData->g<<16)&0x00ff0000) +
((VPData->r<<8)&0x0000ff00);
// next pixel
DData++;
VPData++;
}
// set DIB's Source data
DIBData = m_DPage;
break;
}
//
// draw it to our window
//
// get the windows Device Context
hDC = GetDC( g_hWnd );
// if it's available
if (hDC != NULL)
{
// Get the window dimensions
GetClientRect(g_hWnd, &client);
// Streach it
StretchDIBits( hDC,
0, // Destination top left hand
// corner X Position
0, // Destination top left hand
// corner Y Position
client.right, // Destinations Width
client.bottom, // Destinations height
0, // Source top left hand
// corner's X Position
0, // Source top left hand
// corner's Y Position
320, // Sources width
240, // Sources height
DIBData // Source's data
&m_bi, // Bitmap Info
DIB_RGB_COLORS, // operations
SRCCOPY);
// now release the Device Context.
ReleaseDC( g_hWnd, hDC );
}
}
Natural Selection
One thing that hasn't been addressed at all is driver selection. Currently we have:
VV_SetDriver( 0 );
Which certainly isn't the best solution. A better idea is to save this in a configuration file, we'll call it VVConfig.cfg. The basic idea is to save the Driver number in that file and read and use it during initialization. It's pretty simple, we create a function called GetDriver() which returns the saved driver number. It opens the file, searches for a "Driver" string, then uses the number after that as the driver number we want.
/*************************************************************************
*
* Function: GetDriver()
*
* Desc: Gets the driver number saved in VVConfig.cfg.
*
* Notes: 1) if no file is found will use GDI
*
*************************************************************************/
static int GetDriver( void )
{
FILE *File;
int Driver;
char buf[256];
// default to GDI
Driver = 0;
// open config file
File = fopen( "VVConfig.cfg", "rt" );
if (File)
{
// get string
fscanf( File, "%s", &buf );
// search for Config string
if (strcmp(buf, "Driver") == 0)
{
// get driver setting
fscanf( File, "%i", &Driver );
}
// clenaup
fclose( File );
}
return Driver;
}
We also have to use this function, which is pretty simple. I.e. instead of
// select GDI Device
VV_SetDriver( 0 );
we use:
// select GDI Device
VV_SetDriver( GetDriver() );
This just makes things convenient when we have multiple drivers, just edit the file and it will work, no need to recompile, can be done anywhere (does not require a compiler) and hell it doesn't even require a coder.
Conclusion
Well it's a short one today but we've got a solid frame work now and are ready for DirectDraw in the next tute. I've spiced up the effect, those shades of gray were just getting far to interesting. It's a simple sort of free directional tunnel effect, not the most efficient way of doing but it's easy to muck around with and do a textured /insert any geometric object that can be defined as a function of x, y, z/. Anyway have fun and get ready for DirectDraw.